package com.getpebble.android.common.framework;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import com.getpebble.android.common.core.async.PblAsyncTask;
import com.getpebble.android.common.core.trace.Trace;
import com.getpebble.android.common.model.PreferenceModel;
import com.getpebble.android.common.model.PreferenceModel.Preference;
import com.getpebble.android.common.model.PreferenceModel.Type;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapDifference.ValueDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class PreferenceCache extends ContentObserver {
    private static final String[] CURSOR_PROJECTION = new String[]{"_id", "key", "name", "type", "value"};
    private static final String TAG = PreferenceCache.class.getSimpleName();
    private final Handler mBackgroundHandler;
    private Map<String, ContentValues> mCache = new ConcurrentHashMap();
    private final ContentResolver mContentResolver;
    private final Map<OnSharedPreferenceChangeListener, MultiProcPreferences> mListeners = new ConcurrentHashMap();
    private final HandlerThread mWorkerThread;

    private abstract class KeyChangedCallback {
        abstract void onKeyChanged(String str, String str2);

        private KeyChangedCallback() {
        }
    }

    class LoadPreferencesAsyncTask extends PblAsyncTask {
        private Cursor mLoadedCursor;

        LoadPreferencesAsyncTask() {
        }

        public boolean doInBackground() {
            this.mLoadedCursor = PreferenceCache.this.loadCursor();
            return this.mLoadedCursor != null;
        }

        public void onTaskSuccess() {
            PreferenceCache.this.onLoadComplete(this.mLoadedCursor);
        }

        public void onTaskFailed() {
            Trace.error(PreferenceCache.TAG, "LoadPreferencesAsyncTask onTaskFailed");
        }
    }

    public PreferenceCache(ContentResolver contentResolver) {
        super(null);
        this.mContentResolver = contentResolver;
        this.mContentResolver.registerContentObserver(PreferenceModel.TABLE_URI, true, this);
        this.mWorkerThread = new HandlerThread("PreferenceCache-Worker");
        this.mWorkerThread.start();
        this.mBackgroundHandler = new Handler(this.mWorkerThread.getLooper());
    }

    public synchronized Map<String, ?> getAll(String name) {
        Map<String, ?> contentValuesToMap;
        if (this.mCache.containsKey(name)) {
            contentValuesToMap = contentValuesToMap((ContentValues) this.mCache.get(name));
        } else {
            contentValuesToMap = ImmutableMap.of();
        }
        return contentValuesToMap;
    }

    public synchronized String getString(String name, String key, String defValue) {
        if (this.mCache.containsKey(name)) {
            String result = ((ContentValues) this.mCache.get(name)).getAsString(key);
            if (result == null) {
                result = defValue;
            }
            defValue = result;
        }
        return defValue;
    }

    public synchronized Set<String> getStringSet(String name, String key, Set<String> defValues) {
        if (this.mCache.containsKey(name)) {
            String result = ((ContentValues) this.mCache.get(name)).getAsString(key);
            if (result != null) {
                try {
                    defValues = Sets.newHashSet((String[]) new Gson().fromJson(result, String[].class));
                } catch (Throwable e) {
                    Trace.debug(TAG, e);
                }
            }
        }
        return defValues;
    }

    public synchronized int getInt(String name, String key, int defValue) {
        if (this.mCache.containsKey(name)) {
            Integer result = ((ContentValues) this.mCache.get(name)).getAsInteger(key);
            if (result != null) {
                defValue = result.intValue();
            }
        }
        return defValue;
    }

    public synchronized long getLong(String name, String key, long defValue) {
        if (this.mCache.containsKey(name)) {
            Long result = ((ContentValues) this.mCache.get(name)).getAsLong(key);
            if (result != null) {
                defValue = result.longValue();
            }
        }
        return defValue;
    }

    public synchronized float getFloat(String name, String key, float defValue) {
        if (this.mCache.containsKey(name)) {
            Float result = ((ContentValues) this.mCache.get(name)).getAsFloat(key);
            if (result != null) {
                defValue = result.floatValue();
            }
        }
        return defValue;
    }

    public synchronized boolean getBoolean(String name, String key, boolean defValue) {
        if (this.mCache.containsKey(name)) {
            Boolean result = ((ContentValues) this.mCache.get(name)).getAsBoolean(key);
            if (result != null) {
                defValue = result.booleanValue();
            }
        }
        return defValue;
    }

    public synchronized boolean contains(String name, String key) {
        boolean z;
        z = this.mCache.containsKey(name) && ((ContentValues) this.mCache.get(name)).containsKey(key);
        return z;
    }

    public synchronized void commit(String name, ContentValues preferencesAdded, Set<String> preferencesRemoved, Boolean clearBeforeApplying) {
        final ContentValues added;
        final Set<String> removed;
        Trace.verbose(TAG, "commit(" + name + ")");
        added = new ContentValues(preferencesAdded);
        removed = ImmutableSet.copyOf(preferencesRemoved);
        synchronized (this) {
            if (this.mCache.containsKey(name)) {
                if (clearBeforeApplying.booleanValue()) {
                    for (String key : ((ContentValues) this.mCache.get(name)).keySet()) {
                        notifyListenersKeyChanged(this.mListeners, name, key);
                    }
                    this.mCache.remove(name);
                } else {
                    ContentValues contentValues = (ContentValues) this.mCache.get(name);
                    for (String key2 : removed) {
                        contentValues.remove(key2);
                        notifyListenersKeyChanged(this.mListeners, name, key2);
                    }
                }
            }
            if (added.size() > 0) {
                if (this.mCache.containsKey(name)) {
                    ((ContentValues) this.mCache.get(name)).putAll(added);
                } else {
                    this.mCache.put(name, new ContentValues(added));
                }
                for (String key22 : added.keySet()) {
                    notifyListenersKeyChanged(this.mListeners, name, key22);
                }
            }
        }
        final String str = name;
        final Boolean bool = clearBeforeApplying;
        this.mBackgroundHandler.post(new Runnable() {
            public void run() {
                Trace.verbose(PreferenceCache.TAG, "commit(" + str + ")::run() (updating PreferenceModel in background)");
                if (bool.booleanValue()) {
                    PreferenceModel.removeAll(PreferenceCache.this.mContentResolver, str);
                    removed.clear();
                }
                for (String key : removed) {
                    PreferenceModel.remove(PreferenceCache.this.mContentResolver, str, key);
                }
                for (Entry<String, Object> entry : added.valueSet()) {
                    Type type;
                    String value;
                    String o = entry.getValue();
                    if (o instanceof String) {
                        type = Type.STRING;
                        value = o;
                    } else if (o instanceof Integer) {
                        type = Type.INT;
                        value = String.valueOf(o);
                    } else if (o instanceof Long) {
                        type = Type.LONG;
                        value = String.valueOf(o);
                    } else if (o instanceof Float) {
                        type = Type.FLOAT;
                        value = String.valueOf(o);
                    } else if (o instanceof Boolean) {
                        type = Type.BOOLEAN;
                        value = String.valueOf(o);
                    } else {
                        Trace.error(PreferenceCache.TAG, "Unsupported type, something went wrong; Key: " + ((String) entry.getKey()) + " has class: " + o.getClass());
                    }
                    Trace.verbose(PreferenceCache.TAG, "Writing key = " + ((String) entry.getKey()) + " value = " + value);
                    PreferenceModel.put(PreferenceCache.this.mContentResolver, str, new Preference((String) entry.getKey(), type, value));
                }
            }
        });
    }

    public void addListener(OnSharedPreferenceChangeListener listener, MultiProcPreferences preferences) {
        Trace.verbose(TAG, "addListener()");
        if (this.mListeners.containsKey(listener)) {
            Trace.warning(TAG, "Attempted to add duplicate listener");
        } else {
            this.mListeners.put(listener, preferences);
        }
    }

    public void removeListener(OnSharedPreferenceChangeListener listener) {
        Trace.verbose(TAG, "removeListener()");
        this.mListeners.remove(listener);
    }

    private static void notifyListenersKeyChanged(Map<OnSharedPreferenceChangeListener, MultiProcPreferences> listeners, String preferenceFile, String key) {
        for (Entry<OnSharedPreferenceChangeListener, MultiProcPreferences> entry : listeners.entrySet()) {
            if (((MultiProcPreferences) entry.getValue()).getFile().equals(preferenceFile)) {
                notifyOnMainThread((OnSharedPreferenceChangeListener) entry.getKey(), (SharedPreferences) entry.getValue(), key);
            }
        }
    }

    private static void notifyOnMainThread(final OnSharedPreferenceChangeListener listener, final SharedPreferences preferences, final String key) {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            public void run() {
                try {
                    listener.onSharedPreferenceChanged(preferences, key);
                } catch (Throwable e) {
                    Trace.error(PreferenceCache.TAG, e);
                }
            }
        });
    }

    private static Map<String, ContentValues> createCacheFromCursor(Cursor cursor) {
        Map<String, ContentValues> cache = new ConcurrentHashMap();
        if (cursor == null) {
            Trace.error(TAG, "Error creating cache, cursor is null");
        } else {
            long cacheSizeEstimate = 0;
            while (cursor.moveToNext()) {
                String file = cursor.getString(cursor.getColumnIndex("name"));
                try {
                    cacheSizeEstimate += (long) ((file.length() + cursor.getString(cursor.getColumnIndex("key")).length()) + cursor.getString(cursor.getColumnIndex("value")).length());
                } catch (Exception e) {
                    Trace.verbose(TAG, "Error getting pref cache cursor size");
                }
                ContentValues row = PreferenceModel.cursorRowToContentValue(cursor);
                if (!(row.size() <= 0)) {
                    if (cache.containsKey(file)) {
                        ((ContentValues) cache.get(file)).putAll(row);
                    } else {
                        cache.put(file, row);
                    }
                }
            }
            Trace.debug(TAG, "Size estimate from cursor: " + cacheSizeEstimate);
        }
        return cache;
    }

    private static void diffCaches(Map<String, ContentValues> oldCache, Map<String, ContentValues> newCache, KeyChangedCallback keyChangedCallback) {
        for (Entry<String, ValueDifference<ContentValues>> prefFileDiffEntry : Maps.difference((Map) oldCache, (Map) newCache).entriesDiffering().entrySet()) {
            ContentValues oldValues = (ContentValues) ((ValueDifference) prefFileDiffEntry.getValue()).leftValue();
            ContentValues newValues = (ContentValues) ((ValueDifference) prefFileDiffEntry.getValue()).rightValue();
            if (oldValues == null) {
                for (String key : newValues.keySet()) {
                    keyChangedCallback.onKeyChanged((String) prefFileDiffEntry.getKey(), key);
                }
            } else if (newValues == null) {
                for (String key2 : oldValues.keySet()) {
                    keyChangedCallback.onKeyChanged((String) prefFileDiffEntry.getKey(), key2);
                }
            } else {
                for (Entry<String, ValueDifference<Object>> preferenceDiffEntry : Maps.difference(contentValuesToMap(oldValues), contentValuesToMap(newValues)).entriesDiffering().entrySet()) {
                    keyChangedCallback.onKeyChanged((String) prefFileDiffEntry.getKey(), (String) preferenceDiffEntry.getKey());
                }
            }
        }
    }

    public void onLoadComplete(Cursor cursor) {
        Map<String, ContentValues> newCache = createCacheFromCursor(cursor);
        if (cursor != null) {
            cursor.close();
        }
        synchronized (this) {
            Map<String, ContentValues> oldCache = this.mCache;
            this.mCache = newCache;
            if (!this.mListeners.isEmpty()) {
                diffCaches(oldCache, newCache, new KeyChangedCallback() {
                    void onKeyChanged(String preferenceFile, String key) {
                        PreferenceCache.notifyListenersKeyChanged(PreferenceCache.this.mListeners, preferenceFile, key);
                    }
                });
            }
        }
    }

    public void onChange(boolean selfChange) {
    }

    public void onChange(boolean selfChange, Uri uri) {
        new LoadPreferencesAsyncTask().submit();
    }

    private Cursor loadCursor() {
        long token = Binder.clearCallingIdentity();
        try {
            Cursor cursor = this.mContentResolver.query(PreferenceModel.TABLE_URI, CURSOR_PROJECTION, null, null, "_id ASC");
            if (cursor == null) {
                Trace.error(TAG, "Got null cursor from query!");
                return null;
            }
            Binder.restoreCallingIdentity(token);
            return cursor;
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    public void init() {
        Trace.debug(TAG, "init()");
        onLoadComplete(loadCursor());
    }

    private static Map<String, Object> contentValuesToMap(ContentValues contentValues) {
        Set<Entry<String, Object>> valueSet = contentValues.valueSet();
        Map<String, Object> result = new HashMap(valueSet.size());
        for (Entry<String, Object> value : valueSet) {
            result.put(value.getKey(), value.getValue());
        }
        return result;
    }
}
